home *** CD-ROM | disk | FTP | other *** search
/ Mac Easy 2010 May / Mac Life Ubuntu.iso / casper / filesystem.squashfs / usr / share / pyshared / AppInstall / AppInstall.py < prev    next >
Encoding:
Python Source  |  2009-03-31  |  55.8 KB  |  1,355 lines

  1. # coding: utf-8
  2. #
  3. # Copyright (C) 2004-2005 Ross Burton <ross@burtonini.com>
  4. #               2005-2007 Canonical
  5. #               2006 Sebastian Heinlein
  6. # Authors:
  7. #  Ross Burton
  8. #  Michael Vogt
  9. #  Sebastian Heinlein
  10. #  Ian Jackson
  11. #  Niran Babalola
  12. #
  13. # This program is free software; you can redistribute it and/or modify it under
  14. # the terms of the GNU General Public License as published by the Free Software
  15. # Foundation; either version 2 of the License, or (at your option) any later
  16. # version.
  17. #
  18. # This program is distributed in the hope that it will be useful, but WITHOUT
  19. # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
  20. # FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
  21. # details.
  22. #
  23. # You should have received a copy of the GNU General Public License along with
  24. # this program; if not, write to the Free Software Foundation, Inc., 59 Temple
  25. # Place, Suite 330, Boston, MA 02111-1307 USA
  26.  
  27. # Don't forget to disbale this :)
  28. #import pdb
  29. import gtk
  30. import gtk.glade
  31. import gtk.gdk
  32. import gobject
  33. import gconf
  34. import pango
  35.  
  36. import gettext
  37. from gettext import gettext as _
  38. from math import log
  39.  
  40. #setup gettext
  41. app="gnome-app-install"
  42. gettext.textdomain(app)
  43. gettext.bindtextdomain(app)
  44. gtk.glade.textdomain(app)
  45. gtk.glade.bindtextdomain(app)
  46.  
  47. from widgets.SearchEntry import SearchEntry
  48. from widgets.AppDescView import AppDescView
  49. from widgets.AppListView import AppListView
  50.  
  51. import gc
  52. import stat
  53. import glob
  54. import re
  55. import subprocess
  56. import tempfile
  57. import warnings
  58. import os
  59. import sys
  60. from datetime import datetime
  61. import distros
  62.  
  63. import dbus
  64. import dbus.service
  65. import dbus.glib
  66. import time
  67.  
  68. from warnings import warn
  69. warnings.filterwarnings("ignore", "ICON:.*", UserWarning)
  70. warnings.filterwarnings("ignore", "apt API not stable yet", FutureWarning)
  71. import apt
  72. import apt_pkg
  73.  
  74. # from update-manager, needs to be factored out
  75. from aptsources.sourceslist import SourcesList, is_mirror
  76.  
  77. # internal imports
  78. from DialogComplete import DialogComplete
  79. from DialogPendingChanges import DialogPendingChanges
  80. from DialogMultipleApps import DialogMultipleApps
  81. from DialogProprietary import DialogProprietary
  82. from PackageWorker import PackageWorker
  83. from Menu import ApplicationMenu
  84.  
  85. # FIXME: Share this with update-manager
  86. #        the version in g-a-i is a little bit ahead :)
  87.  
  88. from SimpleGladeApp import SimpleGladeApp
  89. from Progress import GtkOpProgressWindow, GtkCdromProgress
  90. from Util import *
  91. from Cache import MyCache
  92.  
  93. from Menu import (SHOW_ALL,
  94.                   SHOW_ONLY_SUPPORTED,
  95.                   SHOW_ONLY_FREE,
  96.                   SHOW_ONLY_MAIN,
  97.                   SHOW_ONLY_PROPRIETARY,
  98.                   SHOW_ONLY_THIRD_PARTY,
  99.                   SHOW_ONLY_INSTALLED)
  100.  
  101.  
  102. class AppInstallDbusControler(dbus.service.Object):
  103.     """ this is a helper to provide the AppInstallIFace """
  104.     def __init__(self, parent, bus_name,
  105.                  object_path='/org/freedesktop/AppInstallObject'):
  106.         dbus.service.Object.__init__(self, bus_name, object_path)
  107.         self.parent = parent
  108.  
  109.     @dbus.service.method('org.freedesktop.AppInstallIFace')
  110.     def bringToFront(self):
  111.         self.parent.window_main.present()
  112.         return True
  113.     
  114. class AppInstall(SimpleGladeApp):
  115.  
  116.     def __init__(self, options, activation_style):
  117.         #FIXME: we have to disable dbus for the testing infrastructure,
  118.         #       since it runs already in the main loop
  119.         if not options.test_mode:
  120.             self.setupDbus()
  121.  
  122.         self.search_timeout_id = 0
  123.         self.activation_style = activation_style
  124.  
  125.         self.distro = distros.get_distro()
  126.  
  127.         # setup a default icon
  128.         self.icons = gtk.icon_theme_get_default()
  129.         try:
  130.             gtk.window_set_default_icon(self.icons.load_icon("gnome-app-install", 32, 0))
  131.         except gobject.GError:
  132.             pass
  133.  
  134.         SimpleGladeApp.__init__(self, domain="gnome-app-install",
  135.                                 path=options.datadir+"/gnome-app-install.glade")
  136.  
  137.         self.channelsdir = options.desktopdir+"/channels"       
  138.         self.datadir = options.datadir
  139.         self.cachedir = options.cachedir
  140.         self.desktopdir = options.desktopdir
  141.  
  142.         # the GdkWindow of the transient parent
  143.         self.transient_for = None
  144.         if options.transient_for:
  145.             self.transient_for = gtk.gdk.window_foreign_new(options.transient_for)
  146.             if self.transient_for:
  147.                 self.window_main.realize()
  148.                 self.window_main.window.set_transient_for(self.transient_for)
  149.  
  150.         # sensitive stuff
  151.         self.button_ok.set_sensitive(False)
  152.  
  153.         # are we sorting by popcon
  154.         self.sort_by_ranking = False
  155.  
  156.         # setup the gconf backend
  157.         self.config = gconf.client_get_default()
  158.         self.config.add_dir ("/apps/gnome-app-install", gconf.CLIENT_PRELOAD_NONE)
  159.  
  160.         # Tooltips
  161.         self.tooltips = gtk.Tooltips()
  162.         self.tipmap = {}
  163.  
  164.         # Sexy search entry
  165.         self.search_entry = SearchEntry(self.icons)
  166.         self.search_hbox.add(self.search_entry)
  167.         self.search_entry.connect("terms-changed", self._perform_search)
  168.         self.search_entry.show()
  169.  
  170.         self.treeview_packages = AppListView(self.icons)
  171.         self.scrolled_window.add(self.treeview_packages)
  172.         self.treeview_packages.show()
  173.  
  174.         self.textview_description = AppDescView()
  175.         self.scrolled_description.add(self.textview_description)
  176.         self.scrolled_description.set_policy(gtk.POLICY_AUTOMATIC, 
  177.                                              gtk.POLICY_AUTOMATIC)
  178.         msg = _("To install an application check the box next to the "
  179.                 "application. Uncheck the box to remove "
  180.                 "the application.") + "\n"
  181.         msg += _("To perform advanced tasks use the "
  182.                  "Synaptic package manager.")
  183.         header = _("Quick Introduction")
  184.         self.textview_description.show_message(header, msg)
  185.         self.textview_description.show()
  186.  
  187.         # create the treeview
  188.         self.setupTreeview()
  189.  
  190.         # seen flags
  191.         # FIXME: make those @properties that are auto-syned with gconf
  192.         self.components_seen = self.config.get_list("/apps/gnome-app-install/components_seen", "string")
  193.  
  194.         # combobx with filters for the application list
  195.         filter_to_restore = self.config.get_int("/apps/gnome-app-install/filter_applications")
  196.         if filter_to_restore not in range(7):
  197.             filter_to_restore = 0
  198.         list_filters = gtk.ListStore(gobject.TYPE_STRING,
  199.                                      gobject.TYPE_BOOLEAN,
  200.                                      gobject.TYPE_STRING,
  201.                                      gobject.TYPE_INT)
  202.         self.combobox_filter.set_model(list_filters)
  203.         filter_renderer = gtk.CellRendererText()
  204.         self.combobox_filter.pack_start(filter_renderer)
  205.         # Prepare a set of filters
  206.         filters = []
  207.         # Load the primary filters of the distro
  208.         sorted_keys = self.distro.filters_primary.keys()
  209.         sorted_keys.sort()
  210.         for filter in sorted_keys:
  211.             filters.append((self.distro.filters_primary[filter][0],
  212.                             False,
  213.                             self.distro.filters_primary[filter][1],
  214.                             filter))
  215.         # Load the secondary filters of the distro if available and add
  216.         # a separator
  217.         if self.distro.filters_secondary:
  218.             filters.append(("separator", True, "separator", -1))
  219.             sorted_keys = self.distro.filters_secondary.keys()
  220.             sorted_keys.sort()
  221.             for filter in sorted_keys:
  222.                 filters.append((self.distro.filters_secondary[filter][0],
  223.                                 False,
  224.                                 self.distro.filters_secondary[filter][1],
  225.                                 filter))
  226.         # Do not show the installed applications filter in installer only mode
  227.         if not self.activation_style.isInstallerOnly:
  228.             filters.extend([("separator", True, "separator", -1),
  229.                             (_("Installed applications only"), False, 
  230.                              _("Show only applications "\
  231.                                "that are installed on your computer"), 
  232.                              SHOW_ONLY_INSTALLED)])
  233.         for (desc, sep, tooltip, filter) in filters:
  234.             list_filters.append((desc, sep, tooltip, filter))
  235.             self.combobox_filter.set_row_separator_func(self.separator_filter)
  236.             self.combobox_filter.set_cell_data_func(filter_renderer, 
  237.                                                     self.tooltip_on_filter)
  238.             if filter == filter_to_restore:
  239.                 self.combobox_filter.set_active(len(list_filters) - 1)
  240.                 self.tooltips.set_tip(self.eventbox_filter, tooltip)
  241.  
  242.         # connect the changed signal of the combobox
  243.         self.combobox_filter.connect("changed", self.on_combobox_filter_changed)
  244.  
  245.         if self.activation_style.isSpecific():
  246.             self.activation_style.modifyUserInterface(self)
  247.         else:
  248.             # Restore the last state of the main window
  249.             maximized = self.config.get_bool("/apps/gnome-app-install/state/window_maximized")
  250.             height = self.config.get_int("/apps/gnome-app-install/state/window_height")
  251.             width = self.config.get_int("/apps/gnome-app-install/state/window_width")
  252.             if type(maximized) == bool and maximized == True:
  253.                 self.window_main.maximize()
  254.             elif type(width) == int and type(height) == int and \
  255.                  width > 0 and height > 0:
  256.                 self.window_main.set_property("default_width", width)
  257.                 self.window_main.set_property("default_height", height)
  258.  
  259.         if not self.transient_for:
  260.             self.window_main.show()
  261.  
  262.         # check for addon cd and move desktopdir to it
  263.         # FIXME: test if the CD is for the right architecture/release
  264.         self.addon_cd = options.addon_cd
  265.         self.addCD()
  266.  
  267.         # ... and open the cache
  268.         self.updateCache(filter_to_restore)
  269.  
  270.         self.textview_description.hook(self.cache, 
  271.                                        self.menu, 
  272.                                        self.icons,
  273.                                        self.tooltips,
  274.                                        self.distro)
  275.         self.treeview_packages.hook(self.cache,
  276.                                     self.menu)
  277.  
  278.         # move to "All" category per default
  279.         self.treeview_categories.set_cursor((0,))
  280.  
  281.         # this is a set() of packagenames that contain multiple applications
  282.         # if a pkgname is in the set, a dialog was already displayed to the
  283.         # user about this (ugly ...)
  284.         self.multiple_pkgs_seen = set()
  285.         self.window_main.show()
  286.  
  287.         # create a worker that does the actual installing etc
  288.         self.packageWorker = PackageWorker(options.addon_cd)
  289.  
  290.         # Make things responsible
  291.         self.treeview_packages.connect("toggled",
  292.                                        self.on_install_toggle)
  293.         self.treeview_packages.connect("row-activated",
  294.                                        self.on_treeview_packages_row_activated)
  295.         self.treeview_packages.connect("cursor-changed",
  296.                                        self.on_treeview_packages_cursor_changed)
  297.         # now check if the cache is up-to-date
  298.         if not options.test_mode:
  299.             self._checkAptCache()
  300.             
  301.         # make sure the focus is rigth
  302.         if gtk.REALIZED & self.search_entry.flags():
  303.             self.search_entry.grab_focus()
  304.         else:
  305.             self.treeview_packages.grab_focus()
  306.  
  307.     def addCD(self):
  308.         """ check for addon cd and if available and not yet added
  309.             add itadd addon cd
  310.         """
  311.         if self.addon_cd is not None:
  312.             try:
  313.                 self.window_main.set_sensitive(False)
  314.                 self.setBusy(True)
  315.  
  316.                 cd_desktopdir = os.path.join(self.addon_cd,"app-install")
  317.                 if os.path.exists(cd_desktopdir):
  318.                     cdrom = apt.cdrom.Cdrom(progress=GtkCdromProgress(self), mountpoint=self.addon_cd)
  319.                     if not cdrom.inSourcesList:
  320.                         cdrom.add()
  321.                     self.desktopdir=cd_desktopdir
  322.                     self.cachedir=None
  323.             except SystemError, e:
  324.                 print "SystemError while adding addon CD:\n%s" % e
  325.                 header = _("Error reading the addon CD")
  326.                 msg = _("The addon CD may be corrupt ")
  327.                 d = gtk.MessageDialog(parent=self.window_main,
  328.                                       flags=gtk.DIALOG_MODAL,
  329.                                       type=gtk.MESSAGE_ERROR,
  330.                                       buttons=gtk.BUTTONS_CLOSE)
  331.                 d.set_title("")
  332.                 d.set_markup("<big><b>%s</b></big>\n\n%s" % (header, msg))
  333.                 d.realize()
  334.                 d.window.set_functions(gtk.gdk.FUNC_MOVE)
  335.                 d.run()
  336.                 d.destroy()
  337.                 sys.exit(1)
  338.  
  339.  
  340.  
  341.     def _checkAptCache(self):
  342.         # we init with the last time the cache dialog was updated to protect
  343.         # against:
  344.         # problem: what can happen is that the sources.list is modified
  345.         #          but we only get I-M-S hits and the mtime of the Packages
  346.         #          files do not change
  347.         time_cache = self.config.get_int("/apps/gnome-app-install/cache_dialog_time")
  348.         for f in glob.glob("/var/lib/apt/lists/*Packages"):
  349.             mt = os.stat(f)[stat.ST_MTIME]
  350.             ct = os.stat(f)[stat.ST_CTIME] 
  351.             if mt > time_cache:
  352.                time_cache = mt
  353.             if ct > time_cache:
  354.                time_cache = ct
  355.         if not os.path.exists("/etc/apt/sources.list"):
  356.             return 
  357.         time_source = os.stat("/etc/apt/sources.list")[stat.ST_MTIME]
  358.         for f in glob.glob("/etc/apt/sources.list.d/*.list"):
  359.             mt = os.stat(f)[stat.ST_MTIME]
  360.             ct = os.stat(f)[stat.ST_CTIME]
  361.             if mt > time_source:
  362.                 time_source = mt
  363.             if ct > time_source:
  364.                 time_source = ct
  365.         #print "cache:  ", time_cache
  366.         #print "source: ", time_source
  367.         if time_cache < time_source:
  368.             self.dialog_cache_outdated.set_transient_for(self.window_main)
  369.             self.dialog_cache_outdated.realize()
  370.             self.dialog_cache_outdated.window.set_functions(gtk.gdk.FUNC_MOVE)
  371.             res = self.dialog_cache_outdated.run()
  372.             self.dialog_cache_outdated.hide()
  373.             if res == gtk.RESPONSE_YES:
  374.                 self.reloadSources()
  375.                 self.config.set_int("/apps/gnome-app-install/cache_dialog_time", int(time.time()))
  376.  
  377.     def separator_filter(self, model, iter, user_data=None):
  378.         """Used to draw a spearator in the combobox for the filters"""
  379.         return model.get_value(iter, 1)
  380.  
  381.     def setupDbus(self):
  382.         """ this sets up a dbus listener if none is installed alread """
  383.         # check if there is another g-a-i already and if not setup one
  384.         # listening on dbus
  385.         dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
  386.         try:
  387.             bus = dbus.SessionBus()
  388.         except:
  389.             print "warning: could not initiate dbus"
  390.             return
  391.         try:
  392.             proxy_obj = bus.get_object('org.freedesktop.AppInstall', '/org/freedesktop/AppInstallObject')
  393.             iface = dbus.Interface(proxy_obj, 'org.freedesktop.AppInstallIFace')
  394.             iface.bringToFront()
  395.             #print "send bringToFront"
  396.             sys.exit(0)
  397.         except dbus.DBusException, e:
  398.             #print "no listening object (%s) "% e
  399.             try:
  400.                 bus_name = dbus.service.BusName('org.freedesktop.AppInstall',bus)
  401.                 self.dbusControler = AppInstallDbusControler(self, bus_name)
  402.             except Exception, e:
  403.                 print "can't init dbus"
  404.                 return
  405.  
  406.     def setBusy(self, flag):
  407.         """ Show a watch cursor if the app is busy for more than 0.3 sec.
  408.             Furthermore provide a loop to handle user interface events """
  409.         if self.window_main.window is None:
  410.             return
  411.         if flag == True:
  412.             self.window_main.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH))
  413.         else:
  414.             self.window_main.window.set_cursor(None)
  415.         while gtk.events_pending():
  416.             gtk.main_iteration()
  417.  
  418.     def on_combobox_filter_changed(self, combobox):
  419.         """The filter for the application list was changed"""
  420.         self.setBusy(True)
  421.         active = combobox.get_active()
  422.         model = combobox.get_model()
  423.         iter = model.get_iter(active)
  424.         filter_new = model.get_value(iter, 3)
  425.         if filter_new in range(7):
  426.             self.config.set_int("/apps/gnome-app-install/filter_applications", 
  427.                                 filter_new)
  428.             self.refilter(filter=filter_new)
  429.         tooltip = model.get_value(iter, 2)
  430.         self.tooltips.set_tip(self.eventbox_filter, tooltip)
  431.         self.setBusy(False)
  432.  
  433.     def refilter(self, filter=None, terms=None, model=None):
  434.         """
  435.         Applies the given filter or search terms to the menu filter and provides
  436.         a visual feedback for empty results
  437.         """
  438.         if filter != None:
  439.             self.menu.filter = filter
  440.         if terms != None:
  441.             self.menu.searchTerms = terms
  442.         self.menu._refilter(model)
  443.         if len(self.menu.treeview_packages.get_model()) == 0:
  444.              self.show_no_results_msg()
  445.              self.treeview_packages.set_sensitive(False)
  446.         else:
  447.              self.treeview_packages.set_sensitive(True)
  448.              self.menu.treeview_packages.set_cursor(0)
  449.  
  450.     def on_window_main_key_press_event(self, widget, event):
  451.         #print "on_window_main_key_press_event()"
  452.         # from /usr/include/gtk-2.0/gdk/gdkkeysyms.h
  453.         GDK_q = 0x071
  454.         if (event.state & gtk.gdk.CONTROL_MASK) and event.keyval == GDK_q:
  455.             self.on_window_main_delete_event(self.window_main, None)
  456.  
  457.     def error_no_indexfiles(self):
  458.         """ show an error message that no index files could be found
  459.         """
  460.         header = _("The list of applications is not available")
  461.         msg = _("Click on 'Reload' to load it. To reload the "
  462.                 "list you need a working internet connection. ")
  463.         d = gtk.MessageDialog(parent=self.window_main,
  464.                               flags=gtk.DIALOG_MODAL,
  465.                               type=gtk.MESSAGE_INFO)
  466.         d.add_buttons(gtk.STOCK_REFRESH, gtk.RESPONSE_YES,
  467.                       gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE)
  468.         d.set_title("")
  469.         d.set_markup("<big><b>%s</b></big>\n\n%s" % (header, msg))
  470.         d.realize()
  471.         d.window.set_functions(gtk.gdk.FUNC_MOVE)
  472.         res = d.run()
  473.         d.destroy()
  474.         if res == gtk.RESPONSE_YES:
  475.             self.reloadSources()
  476.         
  477.  
  478.     def error_not_available(self, item):
  479.          """Show an error message that the application cannot be installed"""
  480.          header = _("%s cannot be installed on your "
  481.                     "computer type (%s)") % (item.name,
  482.                                              self.cache.getArch())
  483.          msg = _("Either the application requires special hardware features "
  484.                  "or the vendor decided to not support your computer type.")
  485.          d = gtk.MessageDialog(parent=self.window_main,
  486.                                flags=gtk.DIALOG_MODAL,
  487.                                type=gtk.MESSAGE_ERROR,
  488.                                buttons=gtk.BUTTONS_CLOSE)
  489.          d.set_title("")
  490.          d.set_markup("<big><b>%s</b></big>\n\n%s" % (header, msg))
  491.          d.realize()
  492.          d.window.set_functions(gtk.gdk.FUNC_MOVE)
  493.          d.run()
  494.          d.destroy()
  495.  
  496.     def tooltip_on_filter(self, cell_view, cell_renderer, model, iter):
  497.         """
  498.         Show a disclaimer in the tooltips of the filters
  499.         """
  500.         id = model.get_path(iter)[0]
  501.         item_text = model.get_value(iter, 0)
  502.         item_disclaimer = model.get_value(iter, 2)
  503.         cell_renderer.set_property('text', item_text)
  504.         # see LP#87727, no idea why we are sometimes called with
  505.         # this kind of cell_view argument
  506.         if isinstance(cell_view, gtk.TreeViewColumn):
  507.             return
  508.         cell_parent = cell_view.get_parent()
  509.         if (isinstance(cell_parent, gtk.MenuItem) 
  510.             and (cell_parent not in self.tipmap 
  511.                  or self.tipmap[cell_parent] != item_disclaimer)):
  512.             self.tipmap[cell_parent] = item_disclaimer
  513.             self.tooltips.set_tip(cell_parent, item_disclaimer)
  514.  
  515.     def canNotInstallApp(self, pkg):
  516.         """ helper that displays a message if the package can not
  517.             be installed """
  518.         d = gtk.MessageDialog(parent=self.window_main,
  519.                               flags=gtk.DIALOG_MODAL,
  520.                               type=gtk.MESSAGE_ERROR,
  521.                               buttons=gtk.BUTTONS_CLOSE)
  522.         d.set_title("")
  523.         d.set_markup("<big><b>%s</b></big>\n\n%s" % (
  524.             (_("Cannot install '%s'") % pkg),
  525.             (_("This application conflicts with other "
  526.                "installed software. To install '%s' "
  527.                "the conflicting software "
  528.                "must be removed first.\n\n"
  529.                "Switch to the 'synaptic' package manager to resolve this "
  530.                "conflict.") % pkg)))
  531.         d.realize()
  532.         d.window.set_functions(gtk.gdk.FUNC_MOVE)
  533.         d.run()
  534.         d.destroy()
  535.         # reset the cache
  536.         # FIXME: a "pkgSimulateInstall,remove"  thing would
  537.         # be nice
  538.         self.cache.clean()
  539.         return False
  540.  
  541.     def _ensureInArchive(self, item):
  542.         """ check and make sure the package is in available archive """
  543.         pkg = item.pkgname
  544.         if not (self.cache.has_key(pkg) and 
  545.                 self.cache[pkg].candidateDownloadable):
  546.             # first test if we have usable index files
  547.             haveAtLeastOneIndex = False
  548.             for m in self.cache._list.List:
  549.                 if m.URI.startswith("cdrom:"):
  550.                     continue
  551.                 for index in m.IndexFiles:
  552.                     if index.HasPackages and index.Exists:
  553.                         haveAtLeastOneIndex = True
  554.                         break
  555.             if not haveAtLeastOneIndex:
  556.                 self.error_no_indexfiles()
  557.                 return False
  558.             # then test if the file is not available in those
  559.             for it in self.cache._cache.FileList:
  560.                 if (it.Component != "" and it.Component == item.component and
  561.                     it.Archive != "" and 
  562.                     it.Archive == self.distro.get_codename()):
  563.                     self.error_not_available(item)
  564.                     return False
  565.             # check if we have the package in the cache
  566.             self.saveState()
  567.             if not self.addChannel(item):
  568.                 #FIXME: we need a visual feedback here
  569.                 return False
  570.             self.restoreState()
  571.         return True
  572.  
  573.     def tryRemove(self, item):
  574.         #print "tryRemove", item.pkgname
  575.         self.cache.clean()
  576.         pkg = item.pkgname
  577.         # check if removal can be done
  578.         self.cache[pkg].markDelete(autoFix=False)
  579.         if self.cache._depcache.BrokenCount > 0:
  580.             d = gtk.MessageDialog(parent=self.window_main,
  581.                                   flags=gtk.DIALOG_MODAL,
  582.                                   type=gtk.MESSAGE_ERROR,
  583.                                 buttons=gtk.BUTTONS_CLOSE)
  584.             d.set_title("")
  585.             d.set_markup("<big><b>%s</b></big>\n\n%s" % \
  586.                          ((_("Cannot remove '%s'") % pkg),
  587.                         (_("One or more applications depend on %s. "
  588.                            "To remove %s and the dependent applications, "
  589.                            "use the Synaptic package manager.") % (pkg, 
  590.                                                                    pkg))))
  591.             d.realize()
  592.             d.window.set_functions(gtk.gdk.FUNC_MOVE)
  593.             d.run()
  594.             d.destroy()
  595.             self.cache.clean()
  596.             return False
  597.         self.cache.clean()
  598.         return True
  599.  
  600.     def tryInstall(self, item):
  601.         #print "tryInstall", item.pkgname
  602.         pkg = item.pkgname
  603.         self.cache.clean()
  604.         # check if it can be installed savely
  605.         apt_error = False
  606.  
  607.         # get a action group to speed up the calculation
  608.         group = apt_pkg.GetPkgActionGroup(self.cache._depcache)
  609.         # reapply the state of installs
  610.         (to_add, to_rm) = self.menu.getChanges()
  611.         for app in to_add:
  612.             self.cache[app.pkgname].markInstall()
  613.         del group
  614.  
  615.         # now install the new pkg
  616.         try:
  617.             self.cache[pkg].markInstall(autoFix=True)
  618.         except SystemError, e:
  619.             apt_error = True
  620.         except KeyError:
  621.             self.error_not_available(item)
  622.             return False
  623.         # first check if the package savely replaces a
  624.         # existing one
  625.         if (self.cache._depcache.DelCount > 0 and
  626.             self.cache._depcache.BrokenCount == 0 and
  627.             not apt_error):
  628.             if not hasattr(item, "replaces"):
  629.                 return self.canNotInstallApp(pkg)
  630.             item_replaces = set(item.replaces)
  631.             removals = set([pkg.name for pkg in self.cache.getChanges() if pkg.markedDelete])
  632.             if item_replaces != removals:
  633.                 return self.canNotInstallApp(pkg)
  634.             else:
  635.                 # mark the save replace here
  636.                 for r in item_replaces:
  637.                     try:
  638.                         apps = self.menu.pkg_to_app[r]
  639.                         for app in apps:
  640.                             app.toInstall = False
  641.                     except KeyError, e:
  642.                         pass
  643.                 self.treeview_packages.queue_draw()
  644.  
  645.         # check if we do not conflict with something
  646.         for app in to_add:
  647.             if not self.cache[app.pkgname].markedInstall:
  648.                 apt_error = True
  649.         # then check for real errors
  650.         if apt_error or self.cache._depcache.BrokenCount > 0:
  651.             return self.canNotInstallApp(pkg)
  652.         return True
  653.        
  654.     def tryKeep(self, item):
  655.         #print "tryKeep", item.pkgname
  656.         # see if we have replaces and if so, apply them
  657.         if hasattr(item, "replaces"):
  658.             for r in item.replaces:
  659.                 apps = self.menu.pkg_to_app[r]
  660.                 for app in apps:
  661.                     app.toInstall = not app.toInstall
  662.             self.treeview_packages.queue_draw()
  663.         return True
  664.  
  665.     def _ensureUnsupportedOrLegalWarning(self, item):
  666.         """ warn:
  667.             - on potential legal problems (codecs) always
  668.             - when a universe or multiverse package is installed for
  669.               the first time
  670.         """
  671.         # check patentBadness
  672.         if item.patentBadness == True:
  673.             dia = gtk.MessageDialog(parent=self.window_main,
  674.                                     type=gtk.MESSAGE_WARNING,
  675.                                     buttons=gtk.BUTTONS_CANCEL)
  676.             header = _("Confirm installation of restricted software")
  677.             body = _("The use of this software may be "
  678.                      "restricted in some countries. You "
  679.                      "must verify that one of the following is true:\n\n"
  680.                      "* These restrictions do not apply in your country "
  681.                      "of legal residence\n"
  682.                      "* You have permission to use this software (for "
  683.                      "example, a patent license)\n"
  684.                      "* You are using this software for research "
  685.                      "purposes only")
  686.             dia.set_markup("<big><b>%s</b></big>\n\n%s" % (header,body))
  687.             dia.add_button(_("C_onfirm"), gtk.RESPONSE_OK)
  688.             res = dia.run()
  689.             dia.hide()
  690.             if res != gtk.RESPONSE_OK:
  691.                 return False
  692.             return True
  693.  
  694.         # we run the dialog with need_internet = False because we come
  695.         # here always after _ensureInArchive() was run and that will
  696.         # enable the sources.list bits for us
  697.         if (item.component in self.distro.get_components_ask() and 
  698.             item.component not in self.components_seen):
  699.             if not self._confirm_source_activation(item):
  700.                 return False
  701.             self.components_seen.append(item.component)
  702.             self.config.set_list("/apps/gnome-app-install/components_seen",
  703.                                  "string", self.components_seen)
  704.         return True
  705.  
  706.     def _confirm_source_activation(self, item, need_internet=True):
  707.         """
  708.         Ask the user if a specified componet of the distribution 
  709.         should be enabled
  710.         """
  711.         primary=""
  712.         secondary=""
  713.         dia = gtk.MessageDialog(parent=self.window_main,
  714.                                 type=gtk.MESSAGE_QUESTION,
  715.                                 buttons=gtk.BUTTONS_CANCEL)
  716.         messages = self.distro.get_components_ask_msgs()
  717.         if item.component:
  718.             if messages.has_key(item.component):
  719.                 (primary, secondary) = messages[item.component]
  720.             else:
  721.                 (primary, secondary) = messages[None]
  722.                 primary = primary % item.component
  723.         elif item.channel:
  724.             if item.isv:
  725.                 vendor = item.isv
  726.             else:
  727.                 vendor = item.channel
  728.             primary = _("Enable the installation of software "
  729.                         "from %s?") % vendor
  730.             secondary = _("%s is provided by a third party vendor. The third "
  731.                           "party vendor is responsible for support and "
  732.                           "security updates.")
  733.         if need_internet:
  734.             secondary += "\n\n%s" % _("You need a working internet connection "
  735.                                       "to continue.")
  736.         dia.set_markup("<b><big>%s</big></b>" % primary)
  737.         dia.format_secondary_markup(secondary % item.name)
  738.         dia.add_button(_("_Enable"), gtk.RESPONSE_OK)
  739.         ret = dia.run()
  740.         dia.hide()
  741.         if ret == gtk.RESPONSE_OK:
  742.             return True
  743.         else:
  744.             return False
  745.  
  746.     # install toggle on the treeview
  747.     def on_install_toggle(self, widget, item):
  748.         #print "on_install_toggle: %s %s" % (renderer, path)
  749.         self.setBusy(True)
  750.         pkg = item.pkgname
  751.  
  752.         # see what should be done
  753.         want_install = not item.toInstall
  754.         is_installed = self.cache.has_key(pkg) and self.cache[pkg].isInstalled
  755.         
  756.         install = want_install and not is_installed
  757.         remove =  not want_install and is_installed
  758.         keep = (want_install and is_installed or
  759.                 not want_install and not is_installed)
  760.         assert(install ^ remove ^ keep)
  761.  
  762.         # give a first time warning on universe/multiverse package
  763.         # installs (because we enable unverse/multiverse by default now)
  764.         if install:
  765.             if not self._ensureUnsupportedOrLegalWarning(item):
  766.                 self.setBusy(False)
  767.                 return False
  768.             if not self._ensureInArchive(item):
  769.                 self.setBusy(False)
  770.                 return False
  771.  
  772.         # see if it can be done without breaking the cache
  773.         if install and not self.tryInstall(item):
  774.             self.setBusy(False)
  775.             return False
  776.         elif remove and not self.tryRemove(item):
  777.             self.setBusy(False)
  778.             return False
  779.         elif keep and not self.tryKeep(item):
  780.             self.setBusy(False)
  781.             return False
  782.             
  783.         # the status of the selected package
  784.         status = item.toInstall
  785.         # check if the package provides multiple desktop applications
  786.         if len(self.menu.pkg_to_app[item.pkgname]) > 1:
  787.             apps = self.menu.pkg_to_app[item.pkgname]
  788.             # hack: redraw the treeview (to update the toggle icons after the
  789.             #       tree-model was changed)
  790.             self.treeview_packages.queue_draw()
  791.             # show something to the user (if he hasn't already seen it)
  792.             if not item.pkgname in self.multiple_pkgs_seen:
  793.                 dia = DialogMultipleApps(self.datadir, self.window_main,
  794.                                          apps, item.name,
  795.                                          self.cache[pkg].isInstalled)
  796.                 rt = dia.run()
  797.                 dia.hide()
  798.                 self.multiple_pkgs_seen.add(item.pkgname)
  799.                 if rt != gtk.RESPONSE_OK:
  800.                     self.setBusy(False)
  801.                     return
  802.             for app in apps:
  803.                  app.toInstall = not status
  804.         else:
  805.             # invert the current selection
  806.             item.toInstall = not status
  807.         self.button_ok.set_sensitive(self.menu.isChanged())
  808.         self.setBusy(False)
  809.  
  810.     def addChannel(self, item):
  811.         """Ask for confirmation to add the missing channel or
  812.            component of the current selected application"""
  813.         if item.thirdparty and item.channel:
  814.             dia = DialogProprietary(self.datadir, self.window_main, item)
  815.             res = dia.run()
  816.             dia.hide()
  817.             # the user canceld
  818.             if res != gtk.RESPONSE_OK:
  819.                 return False
  820.         else:
  821.             if not self._confirm_source_activation(item):
  822.                 # the user canceld
  823.                 return False
  824.         # let us go
  825.         if item.component:
  826.             if item.component in self.distro.get_components_ask():
  827.                 self.components_seen.append(item.component)
  828.                 self.config.set_list("/apps/gnome-app-install/components_seen",
  829.                                      "string", self.components_seen)
  830.             #FIXME: Should raise an error
  831.             if not self.enableComponent(item.component):
  832.                 return False
  833.  
  834.             # Also enable further components that are required
  835.             for dep in self.distro.get_comp_dependencies(item.component):
  836.                 for it in self.cache._cache.FileList:
  837.                     if it.Component != "" and it.Component == dep:
  838.                         break
  839.                 else:
  840.                     if not self.enableComponent(dep):
  841.                         return False
  842.         elif item.channel:
  843.             if not self.enableChannel(item.channel):
  844.                 return False
  845.         else:
  846.             # should never happen
  847.             print "ERROR: addChannel() called without channel or component"
  848.             return False
  849.         # now do the reload
  850.         self.reloadSources()
  851.         return True
  852.  
  853.     def setupTreeview(self):
  854.         def _icon_cell_func(column, cell, model, iter):
  855.             menuitem = model.get_value(iter, COL_ITEM)
  856.             if menuitem == None or menuitem.iconname == None:
  857.                 cell.set_property("pixbuf", None)
  858.                 cell.set_property("visible", False)
  859.                 return
  860.             try:
  861.                 icon = self.icons.load_icon(menuitem.iconname, 24, 0)
  862.             except gobject.GError:
  863.                 try:
  864.                     icon = self.icons.load_icon("applications-other", 24, 0)
  865.                 except gobject.GError:
  866.                     icon = self.icons.load_icon(gtk.STOCK_MISSING_IMAGE, 24, 0)
  867.             cell.set_property("pixbuf", icon)
  868.             cell.set_property("visible", True)
  869.  
  870.         # categories
  871.         column_cat = gtk.TreeViewColumn("")
  872.         # icons
  873.         renderer_cat_icon = gtk.CellRendererPixbuf()
  874.         column_cat.pack_start(renderer_cat_icon, False)
  875.         column_cat.set_cell_data_func(renderer_cat_icon, _icon_cell_func)
  876.         # categoriy name
  877.         renderer_cat_name = gtk.CellRendererText()
  878.         renderer_cat_name.set_property("scale", 1.0)
  879.         column_cat.pack_start(renderer_cat_name, True)
  880.         column_cat.add_attribute(renderer_cat_name, "markup", COL_CAT_NAME)
  881.         self.treeview_categories.append_column(column_cat)
  882.         self.treeview_categories.set_search_column(COL_CAT_NAME)
  883.  
  884.     def saveState(self):
  885.         """ save the current state of the app """
  886.         # store the pkgs that are marked for removal or installation
  887.         (self.to_add, self.to_rm) = self.menu.getChanges()
  888.         (self.cursor_categories_path,x) = self.treeview_categories.get_cursor()
  889.         model = self.treeview_packages.get_model()
  890.         (packages_path, x) = self.treeview_packages.get_cursor()
  891.         if packages_path:
  892.             it = model.get_iter(packages_path)
  893.             self.cursor_pkgname = model.get_value(it, COL_NAME)
  894.         else:
  895.             self.cursor_pkgname = None
  896.  
  897.     def restoreState(self):
  898.         """ restore the current state of the app """
  899.         # set category
  900.         self.window_main.set_sensitive(False)
  901.         self.treeview_categories.set_cursor(self.cursor_categories_path)
  902.         model = self.treeview_packages.get_model()
  903.         # reapply search
  904.         #query = self.search_entry.get_text()
  905.         #if query:
  906.         #    self.on_search_timeout()
  907.         # remark all packages that were marked for installation
  908.         for item in self.to_add:
  909.             if self.cache.has_key(item.pkgname):
  910.                 try:
  911.                     self.cache[item.pkgname].markInstall(autoFix=True)
  912.                 except SystemError:
  913.                     continue
  914.                 # set the state of the corresponing apps
  915.                 apps = self.menu.pkg_to_app[item.pkgname]
  916.                 for app in apps:
  917.                     app.toInstall = item.toInstall
  918.         # remark all packages that were marked for removal
  919.         for item in self.to_rm:
  920.             if self.cache.has_key(item.pkgname):
  921.                 try:
  922.                     self.cache[item.pkgname].markDelete(autoFix=True)
  923.                 except SystemError:
  924.                     continue
  925.                 # set the state of the corresponing apps
  926.                 apps = self.menu.pkg_to_app[item.pkgname]
  927.                 for app in apps:
  928.                     app.toInstall = item.toInstall
  929.         # restore search
  930.         search_term = self.search_entry.get_text()
  931.         if len(search_term) > 0:
  932.             self._perform_search(None, search_term)
  933.         # find package
  934.         for it in iterate_list_store(model,model.get_iter_first()):
  935.             name = model.get_value(it, COL_NAME)
  936.             # if the app correpsonds to the one selected before select it again
  937.             if name == self.cursor_pkgname:
  938.                 path = model.get_path(it)
  939.                 self.treeview_packages.set_cursor(path)
  940.                 break
  941.         self.window_main.set_sensitive(True)
  942.         # redraw the treeview so that all check buttons are updated
  943.         self.treeview_packages.queue_draw()
  944.  
  945.     def updateCache(self, filter=SHOW_ONLY_SUPPORTED):
  946.         self.window_main.set_sensitive(False)
  947.         self.setBusy(True)
  948.  
  949.         # honor transient_for cmdline arguemnt
  950.         self.window_main.realize()
  951.         progress_transient_for = self.window_main
  952.         if self.transient_for is not None:
  953.             progress_transient_for = self.transient_for
  954.         progress = GtkOpProgressWindow(self.glade, progress_transient_for)
  955.         try:
  956.             if not hasattr(self, "cache"):
  957.                 self.cache = MyCache(progress)
  958.             else:
  959.                 self.cache.open(progress)
  960.         except Exception, e:
  961.             # show an error dialog if something went wrong with the cache
  962.             header = _("Failed to check for installed and available applications")
  963.             msg = _("This is a major failure of your software " 
  964.                     "management system. Please check for broken packages "
  965.                     "with synaptic, check the file permissions and "
  966.                     "correctness of the file '/etc/apt/sources.list' and "
  967.                     "reload the software information with: "
  968.                     "'sudo apt-get update' and 'sudo apt-get install -f'."
  969.                     )
  970.             print e
  971.             d = gtk.MessageDialog(parent=self.window_main,
  972.                                   flags=gtk.DIALOG_MODAL,
  973.                                   type=gtk.MESSAGE_ERROR,
  974.                                   buttons=gtk.BUTTONS_CLOSE)
  975.             d.set_title("")
  976.             d.set_markup("<big><b>%s</b></big>\n\n%s" % (header, msg))
  977.             d.realize()
  978.             d.window.set_functions(gtk.gdk.FUNC_MOVE)
  979.             d.run()
  980.             d.destroy()
  981.             sys.exit(1)
  982.  
  983.         if not hasattr(self, "menu"):
  984.             progress.update(0.5)
  985.             self.menu = ApplicationMenu(self.desktopdir,
  986.                                         self.cachedir,
  987.                                         self.cache,
  988.                                         self.treeview_packages,
  989.                                         progress, filter,
  990.                                         activation_style=self.activation_style)
  991.         else:
  992.             self.menu.refreshAfterCacheChange(progress)
  993.         self.treeview_categories.set_model(self.menu.get_categories_store())
  994.  
  995.         adj = self.scrolled_window.get_vadjustment()
  996.         adj.set_value(0)
  997.  
  998.         self.setBusy(False)
  999.         self.button_ok.set_sensitive(False)
  1000.         self.window_main.set_sensitive(True)
  1001.     
  1002.     def ignoreChanges(self):
  1003.         """
  1004.         If any changes have been made, ask the user to apply them and return
  1005.         a value based on the status.
  1006.         Returns True if the changes should be thrown away and False otherwise
  1007.         """
  1008.         if not self.menu.isChanged():
  1009.             return True
  1010.         (to_add, to_rm) = self.menu.getChanges()
  1011.         # FIXME: move this set_markup into the dialog itself
  1012.         dia = DialogPendingChanges(self.datadir, self.window_main,
  1013.                                    to_add, to_rm)
  1014.         header =_("Apply changes to installed applications before closing?")
  1015.         msg = _("If you do not apply your changes they will be lost "\
  1016.                 "permanently.")
  1017.         dia.label_pending.set_markup("<big><b>%s</b></big>\n\n%s" % \
  1018.                                      (header, msg))
  1019.         dia.button_ignore_changes.set_label(_("_Close Without Applying"))
  1020.         dia.button_ignore_changes.show()
  1021.         dia.dialog_pending_changes.realize()
  1022.         dia.dialog_pending_changes.window.set_functions(gtk.gdk.FUNC_MOVE)
  1023.         res = dia.run()
  1024.         dia.hide()
  1025.         return res
  1026.  
  1027.     
  1028.     # ----------------------------
  1029.     # Main window button callbacks
  1030.     # ----------------------------
  1031.  
  1032.     def on_button_help_clicked(self, widget):
  1033.         if os.path.exists("/usr/bin/yelp"):
  1034.             subprocess.Popen(["/usr/bin/yelp", "ghelp:gnome-app-install"])
  1035.         else:
  1036.             d = gtk.MessageDialog(parent=self.window_main,
  1037.                                   flags=gtk.DIALOG_MODAL,
  1038.                                   type=gtk.MESSAGE_ERROR,
  1039.                                   buttons=gtk.BUTTONS_CLOSE)
  1040.             header = _("No help available")
  1041.             msg = _("To display the help, you need to install the "
  1042.                     "\"yelp\" application.")
  1043.             d.set_title("")
  1044.             d.set_markup("<big><b>%s</b></big>\n\n%s" % (header, msg))
  1045.             d.run()
  1046.             d.destroy()
  1047.  
  1048.  
  1049.     def applyChanges(self, to_add, to_rm):
  1050.         """
  1051.         Install and remove the packages of the given applications and
  1052.         show a status dialog afterwards
  1053.         """
  1054.         self.setBusy(True)
  1055.         # Get the selections delta for the changes and apply them
  1056.         pkgs_add = set([item.pkgname for item in to_add])
  1057.         pkgs_rm = set([item.pkgname for item in to_rm])
  1058.         if len(pkgs_rm) > 0:
  1059.             additional_rm = self.cache.getDependantAutoDeps(pkgs_rm)
  1060.             pkgs_rm |= additional_rm
  1061.         ret = self.packageWorker.perform_action(self.window_main, self.cache,
  1062.                                                 pkgs_add, pkgs_rm)
  1063.         # FIXME: needs to be tested on low end machines
  1064.         self.updateCache(filter=self.menu.filter)
  1065.         # Show window with newly installed programs
  1066.         dia = DialogComplete(self.datadir, self.window_main,
  1067.                              to_add, to_rm, self.cache,
  1068.                              self.activation_style.autoClose())
  1069.         response = dia.run()
  1070.         self.setBusy(False)
  1071.         self.activation_style.changesSuccessfulNotify()
  1072.         if response == gtk.RESPONSE_CLOSE:
  1073.             self.quit()
  1074.         elif response == 1:
  1075.             # The user has chosen to retry
  1076.             self.applyChanges(to_add, to_rm)
  1077.         self.refilter()
  1078.  
  1079.     def on_button_ok_clicked(self, button):
  1080.         (to_add, to_rm) = self.menu.getChanges()
  1081.         # show a confirmation dialog if we are in full mode
  1082.         if (self.activation_style.isInstallerOnly or 
  1083.             self.confirmChanges(to_add, to_rm)):
  1084.             self.activation_style.userApprovedNotify()
  1085.             self.applyChanges(to_add, to_rm)
  1086.  
  1087.     def confirmChanges(self, to_add, to_rm):
  1088.         """
  1089.         Show a dialog that asks the user to confirm the given changes
  1090.         """
  1091.         dia = DialogPendingChanges(self.datadir, self.window_main,
  1092.                                    to_add, to_rm)
  1093.         # FIXME: move this inside the dialog class, we show a different
  1094.         # text for a quit dialog and a approve dialog
  1095.         header = _("Apply the following changes?")
  1096.         msg = _("Please take a final look through the list of "\
  1097.                 "applications that will be installed or removed.")
  1098.         dia.label_pending.set_markup("<big><b>%s</b></big>\n\n%s" % \
  1099.                                      (header, msg))
  1100.         res = dia.run()
  1101.         dia.hide()
  1102.         if res != gtk.RESPONSE_APPLY:
  1103.             # anything but ok makes us leave here
  1104.             return False
  1105.         else:
  1106.             return True
  1107.  
  1108.     def _perform_search(self, widget, query):
  1109.         """
  1110.         Filter and sort by rank being based on the entered terms. Otherwise 
  1111.         sort by name
  1112.         """
  1113.         self.setBusy(True)
  1114.         model = self.treeview_packages.get_model()
  1115.         if query.lstrip() != "":
  1116.             search_terms = query.lower().split(" ")
  1117.             self.sort_by_ranking = True
  1118.             search_terms = query.lower().split(" ")
  1119.             # Avoid to change the sorting if the user already changed it
  1120.             # for the current query
  1121.             if not model.has_default_sort_func():
  1122.                 model.set_default_sort_func(self.menu._ranking_sort_func)
  1123.                 model.set_sort_column_id(-1, gtk.SORT_ASCENDING)
  1124.         else:
  1125.             search_terms = []
  1126.             self.sort_by_ranking = False
  1127.             model.set_sort_column_id(COL_NAME, gtk.SORT_ASCENDING)
  1128.             model.set_default_sort_func(self.menu._name_sort_func)
  1129.         self.refilter(terms=search_terms)
  1130.         self.setBusy(False)
  1131.  
  1132.     def on_reload_activate(self, item):
  1133.         self.reloadSources()
  1134.         
  1135.     def on_button_cancel_clicked(self, item):
  1136.         self.quit()
  1137.  
  1138.     def reloadSources(self):
  1139.         self.window_main.set_sensitive(False)
  1140.         self.saveState()
  1141.         ret = self.packageWorker.perform_action(self.window_main,
  1142.                                                 None,
  1143.                                                 action=PackageWorker.UPDATE)
  1144.         self.updateCache(filter=self.menu.filter)
  1145.         self.restoreState()
  1146.         self.window_main.set_sensitive(True)
  1147.         return ret
  1148.  
  1149.     def enableChannel(self, channel):
  1150.         """ enables a channel with 3rd party software """
  1151.         # enabling a channel right now is very easy, just copy it in place
  1152.         channelpath = "%s/%s.list" % (self.channelsdir,channel)
  1153.         channelkey = "%s/%s.key" % (self.channelsdir,channel)
  1154.         if not os.path.exists(channelpath):
  1155.             print "WARNING: channel '%s' not found" % channelpath
  1156.             return
  1157.         #shutil.copy(channelpath,
  1158.         #            apt_pkg.Config.FindDir("Dir::Etc::sourceparts"))
  1159.         cmd = ["gksu",
  1160.                "--desktop", "/usr/share/applications/gnome-app-install.desktop",
  1161.                "--",
  1162.                "install", "--mode=644","--owner=0",channelpath,
  1163.                apt_pkg.Config.FindDir("Dir::Etc::sourceparts")]
  1164.         subprocess.call(cmd)
  1165.         # install the key as well
  1166.         if os.path.exists(channelkey):
  1167.             cmd = ["gksu",
  1168.                    "--desktop",
  1169.                    "/usr/share/applications/gnome-app-install.desktop",
  1170.                    "--",
  1171.                    "apt-key", "add",channelkey]
  1172.             subprocess.call(cmd)
  1173.         return True
  1174.  
  1175.     def enableComponent(self, component):
  1176.         """ Enables a component of the current distribution
  1177.             (in a seperate file in /etc/apt/sources.list.d/$dist-$comp)
  1178.         """
  1179.         # sanity check
  1180.         if component == "":
  1181.             print "no repo found in enableRepository"
  1182.             return
  1183.  
  1184.         cmd = ["gksu", "--desktop",
  1185.                "/usr/share/applications/gnome-app-install.desktop",
  1186.                "--",
  1187.                "gnome-app-install-helper", "-e", component]
  1188.         try:
  1189.             output = subprocess.Popen(cmd,
  1190.                                       stdout=subprocess.PIPE).communicate()[0]
  1191.         except OSError, e:
  1192.             print >>sys.stderr, "Execution failed:", e
  1193.         #FIXME: Very ugly, but gksu doesn't return the correct exit states
  1194.         if output == "Enabled the %s component\n" % component:
  1195.             return True
  1196.         else:
  1197.             return False
  1198.  
  1199.     # ---------------------------
  1200.     # Window management functions
  1201.     # ---------------------------
  1202.  
  1203.     def on_window_main_delete_event(self, window, event):
  1204.         if window.get_property("sensitive") == False:
  1205.             return True
  1206.         if self.menu.isChanged():
  1207.             ret = self.ignoreChanges()
  1208.             if ret == gtk.RESPONSE_APPLY:
  1209.                 (to_add, to_rm) = self.menu.getChanges()
  1210.                 if not self.applyChanges(to_add, to_rm):
  1211.                     return True
  1212.             elif ret == gtk.RESPONSE_CANCEL:
  1213.                 return True
  1214.             elif ret == gtk.RESPONSE_CLOSE:
  1215.                 self.quit()
  1216.         self.quit()
  1217.  
  1218.     def on_window_main_destroy_event(self, data=None):
  1219.         #if self.window_installed.get_property("visible") == False:
  1220.         #    self.quit()
  1221.         self.quit()
  1222.             
  1223.     def quit(self):
  1224.         """
  1225.         Stores the state of the main window and quits the application
  1226.         """
  1227.         # Save the state of the main window
  1228.         if not self.activation_style.isSpecific():
  1229.             maximized = self.window_main.window.get_state() == gtk.gdk.WINDOW_STATE_MAXIMIZED
  1230.             self.config.set_bool("/apps/gnome-app-install/state/window_maximized", maximized)
  1231.             if not maximized:
  1232.                 (width, height) = self.window_main.get_size()
  1233.                 self.config.set_int("/apps/gnome-app-install/state/window_height", height) 
  1234.                 self.config.set_int("/apps/gnome-app-install/state/window_width", width)
  1235.         # Allow the activation style to quit the application and send special
  1236.         # exit states
  1237.         self.activation_style.quitHook()
  1238.         sys.exit(0)
  1239.  
  1240.     def on_treeview_packages_row_activated(self, treeview, path, view_column):
  1241.         iter = treeview.get_model().get_iter(path)
  1242.         item = treeview.get_model().get_value(iter, COL_ITEM)
  1243.         # We have to do this check manually, since we don't know if the
  1244.         # corresponding CellRendererToggle is not activatable
  1245.         if item.architectures and \
  1246.             self.cache.getArch() not in item.architectures:
  1247.             return False
  1248.         self.on_install_toggle(None, item)
  1249.  
  1250.     def on_treeview_categories_cursor_changed(self, treeview):
  1251.         """
  1252.         Show the applications that belong to the selected category and
  1253.         restore the previos sorting
  1254.         """
  1255.         #print "on_treeview_categories_cursor_changed"
  1256.         path = treeview.get_cursor()[0]
  1257.         iter = treeview.get_model().get_iter(path)
  1258.         (name, item) = treeview.get_model()[iter]
  1259.         # show a busy cursor if the "all" category was selected
  1260.         if path == (0,):
  1261.             self.setBusy(True)
  1262.         # get the sorting of the current app store
  1263.         old_model = self.treeview_packages.get_model()
  1264.         (sort_column, sort_type) = old_model.get_sort_column_id()
  1265.         # if we are in search mode, set a default sort function and sort by it
  1266.         # if the previous store was sorted by it
  1267.         if self.sort_by_ranking:
  1268.             if sort_column == None:
  1269.                 sort_column = -1
  1270.                 sort_type = gtk.SORT_ASCENDING
  1271.             item.applications.set_default_sort_func(self.menu._ranking_sort_func)
  1272.         # if we are not in search mode, but the store still has got 
  1273.         # a search function remove it and sort by name if the it was 
  1274.         # sorted by the default function before
  1275.         elif item.applications.has_default_sort_func():
  1276.             if sort_column == None:
  1277.                 sort_column = COL_NAME
  1278.                 sort_type = gtk.SORT_ASCENDING
  1279.             # for anything but "All" use the sort func that does the
  1280.             # always on top sorting, we don't use it for all because
  1281.             # its a bit slow there
  1282.             if path != (0,):
  1283.                 item.applications.set_default_sort_func(self.menu._name_sort_func)
  1284.             else:
  1285.                 item.applications.set_default_sort_func(None)
  1286.         else:
  1287.             # this can happen if the user right-clicks on "all"
  1288.             # because we do not have a default sort function then
  1289.             # (for performance reasons)
  1290.             sort_column = -1
  1291.             sort_type = gtk.SORT_ASCENDING
  1292.         item.applications.set_sort_column_id(sort_column, sort_type)
  1293.         # hrm, figure out how to set it into the default sorting mode at
  1294.         # startup, this is certianly not the right solution
  1295.         item.applications.set_sort_column_id(-1, sort_type)
  1296.         # filter the apps
  1297.         self.refilter(model=item.applications)
  1298.         self.setBusy(False)
  1299.  
  1300.     def on_treeview_packages_cursor_changed(self, treeview):
  1301.         path = treeview.get_cursor()[0]
  1302.         iter = treeview.get_model().get_iter(path)
  1303.  
  1304.         (name, item, popcon) = treeview.get_model()[iter]
  1305.         self.textview_description.show_description(item)
  1306.  
  1307.     def show_no_results_msg(self):
  1308.         """ Give the user some hints if the search returned 
  1309.             no results"""
  1310.         buffer = self.textview_description.get_buffer()
  1311.         buffer.set_text("")
  1312.         # remove all old tags
  1313.         tag_table = buffer.get_tag_table()
  1314.         tag_table.foreach((lambda tag, table: table.remove(tag)), tag_table)
  1315.         # create a tag for the first line
  1316.         tag_header = buffer.create_tag("first-line",
  1317.                                        weight = pango.WEIGHT_BOLD,
  1318.                                        pixels_above_lines=6)
  1319.         msg = _("There is no matching application available.")
  1320.         iter = buffer.get_start_iter()
  1321.         buffer.insert_with_tags_by_name(iter, msg, "first-line")
  1322.         # If the filter combobox is available and the current filter is not a
  1323.         # primary filter we suggest to choose a primary filter 
  1324.         if self.combobox_filter.get_property("visible") == True:
  1325.             if self.menu.filter == SHOW_ONLY_INSTALLED or \
  1326.                self.menu.filter in self.distro.filters_secondary.keys():
  1327.                 if len(self.distro.filters_primary) == 1:
  1328.                     #TRANSLATORS: %s represents a filter name
  1329.                     msg = _("To broaden your search, choose "
  1330.                             "\"%s\".") % self.distro.filters_primary[self.distro.filters_primary.keys()[0]][0]
  1331.                 elif len(self.distro.filters_primary) == 2:
  1332.                     #TRANSLATORS: %s represents a filter name
  1333.                     msg = _("To broaden your search, choose "
  1334.                             "\"%s\" or \"%s\".") % \
  1335.                           (self.distro.filters_primary[self.distro.filters_primary.keys()[0]][0],
  1336.                            self.distro.filters_primary[self.distro.filters_primary.keys()[1]][0])
  1337.                 else:
  1338.                     #TRANSLATORS: Show refers to the Show: combobox
  1339.                     msg = _("To broaden your search, choose a different "
  1340.                             "\"Show\" item.")
  1341.                 buffer.insert_with_tags(iter, "\n%s" % msg)
  1342.         if self.treeview_categories.get_property("visible") == True and \
  1343.            self.treeview_categories.get_cursor()[0] != (0,):
  1344.             #TRANSLATORS: All refers to the All category in the left list
  1345.             msg = "\n%s" % _("To broaden your search, choose "
  1346.                              "'All' categories.")
  1347.             buffer.insert_with_tags(iter, msg)
  1348.        
  1349. # Entry point for testing in source tree
  1350. if __name__ == '__main__':
  1351.     app = AppInstall(os.path.abspath("menu-data"),
  1352.                      os.path.abspath("data"),
  1353.                      sys.argv)
  1354.     gtk.main()
  1355.